﻿using DefaultDspPlugins.UI.Common.Modulizer.Engine.GUI;
using DefaultDspPlugins.UI.Common.Modulizer.Engine.GUIv2.Controls;
using DefaultDspPlugins.UI.Common.Modulizer.Engine.GUIv2.Editors;
using DefaultDspPlugins.UI.Common.Modulizer.Engine.GUIv2.FrontEndComponents;
using DefaultDspPlugins.UI.Common.Modulizer.Engine.GUIv2.Sequencer;
using SharpDepend;
using SharpDepend.Datatypes;
using SharpDepend.Storage;
using SoundEngine3;
using SoundEngine3.DspPlugins;
using SoundEngine3.SequenceContent;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using ZquenceStudio3.Core.Utils;
using ZquenceStudio3Host.DspPlugins.Synthesizers.Nodesizer.Engine;
using ZquenceStudio3Host.DspPlugins.Synthesizers.Nodesizer.Engine.Commands;
using ZquenceStudio3Host.DspPlugins.Synthesizers.Nodesizer.Engine.Modules.Container;
using ZquenceStudio3Host.Graphics;
using ZquenceStudio3Host.Graphics.Components;

namespace ZquenceStudio3Host.DspPlugins.Synthesizers.Nodesizer
{
    /// <summary>
    /// Buildable synthesizer with nodes as modules with input, output and parameters.
    /// </summary>
    public class ModulizerUIv2 : GuiDspPlugin, IModulizerUI
    {
        public enum PanelsAlignment
        {
            BothHorizontal,
            BothVertical,
            One,
        }

        static ModulizerUIv2()
        {
            DefinedComponents.AddComponent(DefinedComponents.DEFAULT_2D, typeof(Dial));
        }
        
        private BaseModulizer mModulizer;
        private BackEndPanel mBackendLayer;
        private FrontEndPanel mFrontend;
        private PanelsAlignment mAlignment = PanelsAlignment.BothHorizontal;
        private Panel mMiddleDragPanel;
        private double mPosition;
        private ModulizerTrackArea mGrid;
        private CustomNode mTempUIData;
        private Button mTogglePage;
        private bool mInited;
        private bool mFreezeAddModule;

        internal double FrontEndPositionX { get { return mFrontend.RealX; } }
        internal double FrontEndPositionY { get { return mFrontend.RealY; } }

        // Windows.
        private ModuleUIEditorWindow mModuleUIEditorWindow;
        private PanelUIEditorWindow mBaseUIEditorWindow;

        internal ModuleManager mModuleManager { get { return mModulizer.mModuleManager; } }

        internal BackEndPanel Backend { get { return mBackendLayer; } }

        internal FrontEndPanel FrontEnd { get { return mFrontend; } }

        internal ModulizerTrackArea TrackArea { get { return mGrid; } }

        public GroupModule CurrentModuleContainer
        {
            get { return mParent; }
        }

        public ModulizerUIv2(SoundEngineContext application, ModuleManager.ModulizerType type)
        {
            
        }
        
        protected override void InitializeGuiComponents(Panel gui)
        {
            mGrid = new ModulizerTrackArea(this, 100);
            mGrid.SetGraphicsHost(Gui.Display);
            mGrid.Initialize(Application, UndoRedo);
            
            //GoInto(mModulizer.mModuleManager);
            mParent = mModulizer.mModuleManager;

            gui.Events.OnMousePressing2 += Events_OnMousePressing;
            gui.Events.OnMouseMoving += Events_OnMouseMoving1;
            gui.Events.OnMouseUnpressing += Events_OnMouseUnpressing1;

            gui.BackgroundColor = Color.Transparent;
            mBackendLayer = new BackEndPanel(gui.EngineCore, mGrid, this, MobileScreenSupport);
            mBackendLayer.DimensionInProcent = true;
            mBackendLayer.Width = 100;
            mBackendLayer.Height = 100;
            gui.AddPart(mBackendLayer);

            mFrontend = new FrontEndPanel(gui.EngineCore, this);
            mFrontend.DimensionInProcent = true;
            mFrontend.BackgroundColor = Color.Black;
            gui.AddPart(mFrontend);

            mMiddleDragPanel = new Panel(gui.EngineCore);
            mMiddleDragPanel.DimensionInProcent = true;
            mMiddleDragPanel.BackgroundColor = Color.DarkGray;
            mMiddleDragPanel.Events.OnMouseMoving += Events_OnMouseMoving;
            mMiddleDragPanel.Events.OnMouseUnpressing += Events_OnMouseUnpressing;
            gui.AddPart(mMiddleDragPanel);
            UpdateMiddleDragPanel();

            mTogglePage = FrontEndPanel.GetBaseButton(gui.EngineCore);
            mTogglePage.DimensionInProcent = false;
            mTogglePage.Width = 10;
            mTogglePage.X = 90;
            mTogglePage.Label.Text = "<>";
            mTogglePage.OnClicked += MTogglePage_OnClicked;
            gui.AddPart(mTogglePage);

            mModuleUIEditorWindow = new DefaultDspPlugins.UI.Common.Modulizer.Engine.GUIv2.Editors.ModuleUIEditorWindow(gui.EngineCore);
            mModuleUIEditorWindow.DimensionInProcent = true;
            mModuleUIEditorWindow.X = 5;
            mModuleUIEditorWindow.Y = 5;
            mModuleUIEditorWindow.Width = 90;
            mModuleUIEditorWindow.Height = 90;
            mModuleUIEditorWindow.Visible = false;
            mModuleUIEditorWindow.ModuleUIUpdated += MModuleUIEditorWindow_ModuleUIUpdated;
            gui.AddPart(mModuleUIEditorWindow);

            mBaseUIEditorWindow = new DefaultDspPlugins.UI.Common.Modulizer.Engine.GUIv2.Editors.PanelUIEditorWindow(gui.EngineCore);
            mBaseUIEditorWindow.DimensionInProcent = true;
            mBaseUIEditorWindow.X = 5;
            mBaseUIEditorWindow.Y = 5;
            mBaseUIEditorWindow.Width = 90;
            mBaseUIEditorWindow.Height = 90;
            mBaseUIEditorWindow.Visible = false;
            //mBaseUIEditorWindow.ModuleUIUpdated += MModuleUIEditorWindow_ModuleUIUpdated;
            gui.AddPart(mBaseUIEditorWindow);

            mModulizer.mModuleManager.OnAddedModule += MModuleManager_OnAddedModule;
            mModulizer.mModuleManager.OnRemovedModule += MModuleManager_OnRemovedModule;
            
            // If UI data has been loaded. Use it.
            if (mTempUIData != null)
            {
                OnLoadDataUI(mTempUIData);
            }

            LazyInit();
            mInited = true;
            this.GoInto(this.CurrentModuleContainer, true);
        }


        private struct ToggleAnimation
        {
            public double Old;
            public double New;
        }
        private CurrentAnimation<ToggleAnimation> mAnimation;

        private void MTogglePage_OnClicked(object sender, double x, double y, int id)
        {
            if (mPosition > 0)
            {
                if (mAnimation == null)
                {
                    mAnimation = AnimationUtils<ToggleAnimation>.Run(new TimeSpan(0, 0, 0, 0, 500), ToggleAnimationTick,
                                AnimationBase.Tanh2,
                                new ToggleAnimation
                                {
                                    Old = mPosition,
                                    New = 0
                                });
                }
            }
            else
            {
                if (mAnimation == null)
                {
                    mAnimation = AnimationUtils<ToggleAnimation>.Run(new TimeSpan(0, 0, 0, 0, 500), ToggleAnimationTick,
                                AnimationBase.Tanh2,
                                new ToggleAnimation
                                {
                                    Old = mPosition,
                                    New = 100
                                });
                }
            }
        }

        internal void AddModuleToFrontEnd(BaseModule module, bool addUIModule)
        {
            if (addUIModule)
            {
                if ((module is GroupModule ? true : module.Parameters.Count != 0))
                {
                    if (this.IsTopModule(module) && !this.mModulizer.mModuleManager.IsProtectedModule(module))
                    {
                        this.mFrontend.AddModule(module, false);
                        return;
                    }
                    this.mFrontend.AddModule(module, true);
                }
            }
        }

        void ToggleAnimationTick(double value, bool finish, ToggleAnimation data)
        {
            if (finish)
            {
                mAnimation = null;
                mPosition = data.New;
            }
            else
            {
                mPosition = data.Old + (data.New - data.Old) * value;
            }

            UpdateMiddleDragPanel();
        }

        private bool Events_OnMousePressing(object sender, double x, double y, int id)
        {
            mGrid.MouseDown(x, y, id);

            return false;
        }

        private void Events_OnMouseMoving1(object sender, double x, double y, int id)
        {
            mGrid.MouseMove(x, y, id);
        }

        private void Events_OnMouseUnpressing1(object sender, double x, double y, int id)
        {
            mGrid.MouseUp(x, y, id);
        }

        protected override void OnSaveDataUI(CustomNode node)
        {
            CustomNode moduleUIPanels = new CustomNode();
            moduleUIPanels.Name = "ModuleUIPanels";
            node.AddNode(moduleUIPanels);

            // If UI not has been loaded.
            if (mFrontend == null)
            {
                // Just use the temporary loaded data if available.
                if (mTempUIData != null)
                {
                    node.Reset(mTempUIData);
                    mTempUIData = null;
                }
            }
            else
            {
                mFrontend.SaveModuleUIPanels(moduleUIPanels);
            }
        }

        protected override void OnLoadDataUI(CustomNode node)
        {
            // UI may not been loaded yet.
            if (mFrontend == null)
            {
                // Save node and load it later when UI is available.
                mTempUIData = node;
            }
            else
            {
                CustomNode moduleUIPanels = node.Get("ModuleUIPanels");
                mFrontend.LoadModuleUIPanels(moduleUIPanels, mModulizer);
            }
        }

        internal bool IsInArea(double x, double y)
        {
            bool isInArea = mBackendLayer.IsInArea(x, y);

            bool isInFrontEnd = mFrontend.IsMouseOver(x, y, 0);

            bool isInDragPnale = mMiddleDragPanel.IsMouseOver(x, y, 0);

            bool isInEditWindow = mModuleUIEditorWindow.Visible && mModuleUIEditorWindow.IsMouseOver(x, y, 0);

            return isInArea && !isInFrontEnd && !isInDragPnale && !isInEditWindow;
        }

        /// <summary>
        /// Calls when a ModuleUI has been updated by user.
        /// </summary>
        /// <param name="moduleUI"></param>
        private void MModuleUIEditorWindow_ModuleUIUpdated(BaseModuleUI moduleUI)
        {
            mFrontend.UpdateModule(moduleUI);
        }

        public void ShowModuleUIEditor(BaseModuleUI moduleUI, ModulizerUIv2 modulizerUI)
        {
            if (!mModuleUIEditorWindow.Visible)
            {
                ModuleUIPanel moduleUIPanel = new ModuleUIPanel(Gui, moduleUI, this);
                moduleUIPanel.LoadStructure();
                mModuleUIEditorWindow.CurrentModuleUI = moduleUIPanel;
                mModuleUIEditorWindow.Show();
                mGrid.TouchEnabled = false;
            }
        }

        private Panel mBaseUIPanelLayout;

        public void ShowModuleUIEditor(Panel panel, ModulizerUIv2 modulizerUI)
        {
            if (!mModuleUIEditorWindow.Visible)
            {
                if (mBaseUIPanelLayout == null)
                {
                    mBaseUIPanelLayout = new Panel(Gui);
                }

                mBaseUIEditorWindow.CurrentPanelUI = mBaseUIPanelLayout;
                mBaseUIEditorWindow.Show();
                mGrid.TouchEnabled = false;
            }
        }

        public void HideModuleUIEditor()
        {
            mModuleUIEditorWindow.Hide();
            mBaseUIEditorWindow.Hide();
            mGrid.TouchEnabled = true;
        }

        protected override void OnShown()
        {
            mGrid.Enabled = true;
            mGrid.TouchEnabled = true;
        }

        protected override void OnHide()
        {
            mGrid.Enabled = false;
            mGrid.TouchEnabled = false;
        }

        protected override void OnRender(IGraphicsDevice canvas)
        {
            double realHeight = this.mBackendLayer.Toolbar.RealHeight * 3;
            this.mTogglePage.Height = this.mBackendLayer.Toolbar.RealHeight;
            this.mTogglePage.X = (double)base.Gui.Display.GraphicsDevice.RenderWidth - realHeight;
            this.mTogglePage.Width = realHeight;

            // Only render track area if it is visible.

            if (mPosition > 0)
            {
                // Render grids.
                mGrid.Draw();
            }

            mFrontend.Visible = mPosition != 100;

            // Render the rest of the UI (panels and buttons)
            base.OnRender(canvas);
        }

        private void Events_OnMouseMoving(object sender, double x, double y, int id)
        {
            mGrid.TouchEnabled = false;

            if (y < Gui.Display.GraphicsDevice.RenderHeight * 0.20 ||
                y > Gui.Display.GraphicsDevice.RenderHeight * 0.80)
            {
                SetAlignmentPosition(PanelsAlignment.BothVertical, true);
            }
            else if (x < Gui.Display.GraphicsDevice.RenderWidth * 0.20 ||
                x > Gui.Display.GraphicsDevice.RenderWidth * 0.80)
            {
                SetAlignmentPosition(PanelsAlignment.BothHorizontal, true);
            }

            switch (mAlignment)
            {
                case PanelsAlignment.BothHorizontal:
                    {
                        mPosition = Limit(mMiddleDragPanel.ConvertPixelsToProcentX(x));
                        UpdateMiddleDragPanel();
                    }
                    break;
                case PanelsAlignment.BothVertical:
                    {
                        mPosition = Limit(mMiddleDragPanel.ConvertPixelsToProcentY(y));
                        UpdateMiddleDragPanel();
                    }
                    break;
                case PanelsAlignment.One:
                    break;
                default:
                    break;
            }
        }

        private void Events_OnMouseUnpressing(object sender, double x, double y, int id)
        {
            mGrid.TouchEnabled = true;
        }

        double Limit(double value)
        {
            return value > 100 ? 100 : value < 0 ? 0 : value;
        }

        public void SetAlignmentPosition(PanelsAlignment alignment, bool animate)
        {
            if (mAlignment != alignment)
            {
                mAlignment = alignment;

                switch (alignment)
                {
                    case PanelsAlignment.BothHorizontal:
                        // TODO: Animate.
                        break;
                    case PanelsAlignment.BothVertical:
                        // TODO: Animate.
                        break;
                    case PanelsAlignment.One:
                        // TODO: Animate.
                        break;
                    default:
                        break;
                }
            }
        }

        private void UpdateMiddleDragPanel()
        {
            switch (mAlignment)
            {
                case PanelsAlignment.BothHorizontal:

                    mFrontend.X = 50;
                    mFrontend.Width = 50;
                    mFrontend.Height = 100;

                    mFrontend.Y = 0;
                    mFrontend.X = mPosition;
                    mFrontend.Width = 100 - mFrontend.X;

                    mMiddleDragPanel.Width = 5;
                    mMiddleDragPanel.Y = 40;
                    mMiddleDragPanel.Height = 20;
                    mMiddleDragPanel.X = mFrontend.X - mMiddleDragPanel.Width * 0.5;
                    break;
                case PanelsAlignment.BothVertical:

                    mFrontend.Y = 50;
                    mFrontend.Height = 50;
                    mFrontend.Width = 100;

                    mFrontend.X = 0;
                    mFrontend.Y = mPosition;
                    mFrontend.Height = 100 - mFrontend.Y;

                    mMiddleDragPanel.Height = 5;
                    mMiddleDragPanel.X = 40;
                    mMiddleDragPanel.Width = 20;
                    mMiddleDragPanel.Y = mFrontend.Y - mMiddleDragPanel.Height * 0.5;
                    break;
                case PanelsAlignment.One:
                    break;
                default:
                    break;
            }
        }

        protected override void OnSetDspPlugin(BaseDspPluginInstance dsp)
        {
            mModulizer = dsp as BaseModulizer;
            mModulizer.SetUI(this);
        }

        public override void OnParametersLoaded()
        {

        }

        private List<BaseModule> mLazyInitTrackModules = new List<BaseModule>();
        private Dictionary<BaseModule, TrackModulePanel> mTrackModules = new Dictionary<BaseModule, TrackModulePanel>();
        private GroupModule mParent;

        bool IsTopModule(BaseModule module)
        {
            if (module.ParentModule != null && module.ParentModule == mModuleManager)
            {
                return true;
            }

            return false;
        }

        private void MModuleManager_OnAddedModule(BaseModule module)
        {
            if (this.mInited && !this.mFreezeAddModule)
            {
                this.AddModule(module, true);
            }
        }

        private void AddModule(BaseModule module, bool addUIModule)
        {
            AddModuleToFrontEnd(module, addUIModule);

            if (Gui == null)
            {
                mLazyInitTrackModules.Add(module);
            }
            else
            {
                AddTrackModulePanel(module);
            }
        }

        private void AddTrackModulePanel(BaseModule module)
        {
            TrackModulePanel trackModulePanel = new TrackModulePanel(module, Gui);

            mTrackModules.Add(module, trackModulePanel);
        }

        public override void BeginLoadData()
        {
            this.mFreezeAddModule = true;
        }

        public override void EndLoadData()
        {
            this.mFreezeAddModule = false;
        }

        private void MModuleManager_OnRemovedModule(BaseModule obj)
        {
            Removemodule(obj, true);
        }

        private void Removemodule(BaseModule obj, bool removeUIModule)
        {
            // Only add module to front end if the module is a top module (has no parent)
            // and is not a protected module such as Out/MidiIn ect.
            if (removeUIModule)
            {
                if (IsTopModule(obj) && !mModulizer.mModuleManager.IsProtectedModule(obj))
                {
                    mFrontend.RemoveModule(obj, false);
                }
                else
                {
                    mFrontend.RemoveModule(obj, true);
                }
            }

            if (Gui == null)
            {
                mLazyInitTrackModules.Remove(obj);
            }
            else
            {
                mTrackModules.Remove(obj);
            }
        }

        private void ClearUIModules()
        {
            List<BaseModule> uiModules = new List<BaseModule>(mTrackModules.Keys);
            foreach (var uiModule in uiModules)
            {
                //MModuleManager_OnRemovedModule(uiModule);
                Removemodule(uiModule, false);
            }
        }

        private void RebuildSynthGraph(IModuleContainer parent)
        {
            foreach (var subModule in parent.SubModules)
            {
                //MModuleManager_OnAddedModule(subModule);
                AddModule(subModule, false);
            }
        }
        
        internal void GoInto(BaseModule module, bool animateCameraToFill = true)
        {
            if (module is GroupModule)
            {
                // Clear all ui modules.
                ClearUIModules();

                mParent = module as GroupModule;

                // Rebuild with new ui modules.
                RebuildSynthGraph(mParent);

                if (animateCameraToFill)
                {
                    mGrid.AnimateCameraToFillAllObjects();
                }

                mGrid.Editor.SelectorHandler.ClearSelection();
            }
        }

        internal void MakeGroup(ReadOnlyCollection<ITransformSequenceObject> selectedReferences)
        {
            List<BaseModule> modules = new List<BaseModule>(selectedReferences.Count);
            foreach (TrackModulePanel modulePanel in selectedReferences)
            {
                if (mModuleManager.IsProtectedModule(modulePanel.Module))
                {
                    SharpDepend.Manager.DialogManager.Show("Can not make group when it contains protected modules.", "Make group", SharpDepend.DialogIcon.Warning);

                    return;
                }
                modules.Add(modulePanel.Module);
            }

            MakeGroupCommand command = new MakeGroupCommand(this, modules);
            UndoRedo.AddAndRunCommand(command);

            mGrid.Editor.SelectorHandler.ClearSelection();
        }

        private void LazyInit()
        {
            if (mLazyInitTrackModules.Count > 0)
            {
                foreach (var module in mLazyInitTrackModules)
                {
                    AddTrackModulePanel(module);
                }
                mLazyInitTrackModules.Clear();
            }

            //UpdateModuleUIDown(mModuleManager);
        }

        private void InvalidateAllModules()
        {
            foreach (var module in mModulizer.mModuleManager.Modules)
            {
                MModuleManager_OnAddedModule(module);
            }
        }

        internal void UpdateModuleForEdit(BaseModule module)
        {

        }

        internal ITransformSequenceObject GetModulePanel(BaseModule module)
        {
            if (mTrackModules.ContainsKey(module))
            {
                return mTrackModules[module];
            }

            return null;
        }
        
        internal void UpdateModuleUIUp(BaseModule module)
        {
            // UI has not yet been loaded. We cant update the UI.
            if (mFrontend != null)
            {
                UpdateModuleUIRecursiveUp(module);

                // Update front end UI.
                mFrontend.UpdateLayout();

                // Update backend module UI.
                mGrid.RefreshCache();
            }
        }

        private void UpdateModuleUIRecursiveUp(BaseModule module)
        {
            if (module != mModuleManager)
            {
                ModuleUIPanel moduleui = mFrontend.GetModuleUIPanel(module);
                mFrontend.UpdateModule(moduleui.ModuleUI);

                // Recursive.
                if (module.ParentModule != null)
                {
                    UpdateModuleUIUp(module.ParentModule);
                }
            }
        }

        internal ModuleUIPanel GetModuleUIPanel(BaseModule module)
        {
            return mFrontend.GetModuleUIPanel(module);
        }

        internal TrackModulePanel GetModuleUIPanel(BaseModule module, BaseModule superGroupOf)
        {
            // If the module contains in super group of.
            BaseModule middle;
            if (module.IsSuperGroupOf(superGroupOf, out middle))
            {
                if (mTrackModules.ContainsKey(middle))
                {
                    return mTrackModules[middle];
                }

                return null;
            }

            if (mTrackModules.ContainsKey(module))
            {
                return mTrackModules[module];
            }

            return null;
        }

        internal TrackModulePanel CreateModule()
        {
            Type item = mBackendLayer.Toolbox.SelectedModule;

            if (item != null)
            {
                lock (SoundEngine.SoundLocker)
                {
                    BaseModule module = ModulesRegistration.CreateInstance(item, mModulizer.mModuleManager);
                    module.GuiPosition.Width = 20;
                    module.GuiPosition.Height = 1;
                    return AddModule(module);
                }
            }

            return null;
        }

        /// <summary>
        /// Add a module to the Modulizer.
        /// </summary>
        /// <param name="module"></param>
        /// <returns></returns>
        public TrackModulePanel AddModule(BaseModule module)
        {
            // Add to engine.
            CurrentModuleContainer.AddSubModule(module);
            //mModuleManager.AddSubModule(module);
            
            // Add to front end.
            //ModuleUIPanel moduleUIPanel = mFrontend.AddModule(module, false);

            //AddTrackModulePanel(module);

            //mCurrentModuleContainer.AddSubModule(module);

            return mTrackModules[module];
        }

        internal void UpdateParameterStates()
        {
            mModulizer.UpdateParameterStates();
        }

        public void OnAddedModule(BaseModule module)
        {
        }

        public void OnRemovedModule(BaseModule module)
        {
        }

        void IModulizerUI.RebuildSynthGraph(IModuleContainer moduleContainer)
        {
            // If grid is null, then ui is unititialized. GoInto will never work.
            if (mGrid != null)
            {
                // Just zoom out everything.
                GoInto(moduleContainer as BaseModule);
            }
        }

        public void SetCustomData(CustomNode custom)
        {
        }

        public void GetCustomData(CustomNode customData)
        {
        }
    }
}
